package com.sql.mysql.sharding.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

import javax.sql.DataSource;

import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.sql.mysql.sharding.threadlocal.ShardingThreadLocal;
import com.sql.mysql.sharding.utils.ShardingUrlUtils;

import lombok.extern.slf4j.Slf4j;

//: 这里考虑复制出一个properties出来,防止有覆盖情况
//看过Druid的方式，是不会有此问题,别的数据连接池未测试，所以复制出1个最保险！
@Slf4j
public class MasterSlaveDataSource implements DataSource {
	private static AtomicInteger COUNTER = new AtomicInteger(0);
	private DataSource masterDataSource = null;
	private DataSource[] slaveDataSources = null;

	public MasterSlaveDataSource(Properties properties) {
		// 确保一开始就init
		properties.put("init", "true");
		try {
			ArrayList<String> urls = ShardingUrlUtils.handle(properties.getProperty("url"));
			slaveDataSources = new DataSource[urls.size() - 1];
			int index = 0;
			for (String url : urls) {
				properties.setProperty("url", url);
				if (0 == index) {
					properties.put("defaultReadOnly", "false");
					masterDataSource = DruidDataSourceFactory.createDataSource(properties);
				} else {
					properties.put("defaultReadOnly", "true");
					slaveDataSources[index - 1] = DruidDataSourceFactory.createDataSource(properties);
				}
				index++;
			}
		} catch (Exception e) {
			log.error(e.toString());
			System.exit(-1);
		}

	}

	// 暂时不需要使用
	@SuppressWarnings("unused")
	private static MasterSlaveType getMasterOrSlave() {
		// 1)get all variables
		boolean autoCommit = ShardingThreadLocal.AutoCommitThreadLocal.get();
		MappedStatement mappedStatement = ShardingThreadLocal.MappedStatementThreadLocal.get();
		String sql = ShardingThreadLocal.BoundSqlThreadLocal.get().getSql();
		SqlCommandType type = mappedStatement.getSqlCommandType();
		// 2)use them
		if (false == autoCommit) {
			return MasterSlaveType.MASTER;
		} else {
			if (SqlCommandType.SELECT != type) {
				log.debug("get Connection by MasterSlaveDataSource--->use masterDataSource");
				return MasterSlaveType.MASTER;
			} else {
				if (-1 != sql.indexOf("/*FORCE_MASTER*/")) {
					log.debug("get Connection by MasterSlaveDataSource--->use masterDataSource");
					return MasterSlaveType.MASTER;
				} else {
					log.debug("get Connection by MasterSlaveDataSource--->use slaveDataSources");
					return MasterSlaveType.SLAVE;
				}
			}
		}
	}

	@Override
	public DruidPooledConnection getConnection() throws SQLException {
		long begin = System.currentTimeMillis();
		try {
			// 1)get all variables
			boolean autoCommit = ShardingThreadLocal.AutoCommitThreadLocal.get();
			//
			String sql = ShardingThreadLocal.BoundSqlThreadLocal.get().getSql();
			MappedStatement mappedStatement = ShardingThreadLocal.MappedStatementThreadLocal.get();
			SqlCommandType type = mappedStatement.getSqlCommandType();
			// 2)use them
			if (false == autoCommit) {
				//
				// LOGGER.debug("get Connection by MasterSlaveDataSource--->use
				// masterDataSource");
				return (DruidPooledConnection) masterDataSource.getConnection();
			} else {
				//
				if (SqlCommandType.SELECT != type) {
					//
					log.debug("get Connection by MasterSlaveDataSource--->use masterDataSource");
					return (DruidPooledConnection) masterDataSource.getConnection();
				} else {
					//
					if (-1 != sql.indexOf("/*FORCE_MASTER*/")) {
						log.debug("get Connection by MasterSlaveDataSource--->use masterDataSource");
						return (DruidPooledConnection) masterDataSource.getConnection();
					} else {
						log.debug("get Connection by MasterSlaveDataSource--->use slaveDataSources");
						//
						int slaveIndex = COUNTER.getAndIncrement();
						if (slaveIndex < 0) {
							slaveIndex = 0;
						}
						//
						slaveIndex = slaveIndex % slaveDataSources.length;
						// ok,let us fetch the connection
						for (int i = 0; i < slaveDataSources.length; i++) {
							DruidPooledConnection connection = (DruidPooledConnection) slaveDataSources[(slaveIndex++)
									% slaveDataSources.length].getConnection();
							if (null != connection) {
								return connection;
							}
						}
						throw new SQLException(
								"fail to get slave mysql connection,maybe all slave machines DOWN,check it now!");
					}
				}
			}
		} finally {
			long end = System.currentTimeMillis();
			long cost = end - begin;
			log.debug("get Druid Connection cost ---> " + cost + " ms");
			// ThreadLocalHelper.FetchConnectionTimeThreadLocal.set(cost);
		}

	}

	// private static int random(int min, int max) {
	// Random random = new Random();
	// int selectedIndex = random.nextInt(max) % (max - min + 1) + min;
	// LOGGER.info("" + selectedIndex);
	// return selectedIndex;
	// }

	public static void main(String[] args) {
		for (int index = 0; index < 100; index++) {
			// LOGGER.info("" + random(0, 2));
		}
	}

	@Override
	public PrintWriter getLogWriter() throws SQLException {
		throw new SQLException("not supported method called");
		// return null;
	}

	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
		throw new SQLException("not supported method called");
	}

	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
		throw new SQLException("not supported method called");
	}

	@Override
	public int getLoginTimeout() throws SQLException {
		throw new SQLException("not supported method called");
		// return 0;
	}

	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		throw new SQLFeatureNotSupportedException("not supported method called");
		// return null;
	}

	@Override
	public <T> T unwrap(Class<T> iface) throws SQLException {
		throw new SQLException("not supported method called");
		// return null;
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		throw new SQLException("not supported method called");
		// return false;
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		throw new SQLException("not supported method called");
		// return null;
	}
}
